#! /usr/bin/env python
# coding=utf-8
"""
# @copyright: Copyright (c) Huawei Technologies Co., Ltd. 2012-2018. All rights reserved.
# @Author: y00447189
# @Date: 2020-10-22 16:59:16
# @LastEditTime: 2020-10-22 11:17:42
# @LastEditors: y00447189
# @Description:
# @FilePath: getpkgship.py
# @可以输入预定的版权声明、个性签名、空行等
"""

import os
import shutil

import subprocess
import enum
import yaml
# import autoimport.getpkgship.getpkgship_logger
from autoimport.logmodule import logger

CURRENT_PATH = os.path.dirname(os.path.abspath(__file__))
OUTPUT_PATH = "output"
CONFIG_PATH = "config"
TMP_FILE_PATH = "tmp"
LOG_PATH = "log"
# 日志
this_logger = logger.init('getpkgship')
logger.add_file_handler(this_logger, "get_rpm_by_pkgship", "getpkgs")


class InputData(object):
    """
    模块输入的数据
    """
    # 输入的待查询软件包列表
    input_pkg = set()
    # 可信的分支
    credible_branches = []
    # 工作分支
    working_branches = []


class OutputData(object):
    """
    模块输出的数据
    """
    # 需要新引入的包
    new_pkg = []
    # 需要从Factory分支合入Mainline的包
    factory_to_mainline_pkg = set()
    # 可以从可信分支拉到工作分支的包
    credible_to_working_pkg = []
    # 本次查询软件包列表的所有依赖包汇总
    all_requires_pkg = []
    # 可信分支中已编译成功的包
    found_credible_pkg = []
    # 工作分支中已编译成功的包
    found_working_pkg = []


class FilterStatus(enum.Enum):
    """
    筛选pkgship输出内容状态标注
    """
    NotStarted = 1
    ReadyToStart = 2
    AlreadyStarted = 3


def get_file_path(sub_dir, file_name):
    """
    根据子目录与文件拼接处文件路径
    :param sub_dir:
    :param file_name:
    :return:
    """
    if str(sub_dir) != "":
        sub_dir = os.path.join(CURRENT_PATH, sub_dir)
        file_path = os.path.join(sub_dir, file_name)
    else:
        file_path = os.path.join(CURRENT_PATH, file_name)
    return file_path


def init_workspace():
    """
    初始化工作目录
    :return:
    """
    dirs = [OUTPUT_PATH]
    for init_dir in dirs:
        # 初始化路径
        dir_path = os.path.join(CURRENT_PATH, init_dir)
        if os.path.exists(dir_path):
            shutil.rmtree(dir_path)
        print("init_workspace dir path:", dir_path)
        os.makedirs(dir_path, exist_ok=True)


def init_branches():
    """
    初始化搜索分支
    :return: 无
    """
    conf_path = get_file_path(CONFIG_PATH, 'branchesDefine.yaml')
    if not os.path.isfile(conf_path):
        this_logger.error(
            "Not found the branchesDefine.yaml file:%s.", conf_path)
        return False

    with open(conf_path, "r") as conf_file:
        file_data = yaml.safe_load(conf_file)
        if 'credible_branches' in file_data:
            InputData.credible_branches = list_unique(
                file_data["credible_branches"])
            this_logger.info("Init credible branches num:%d.",
                             len(InputData.credible_branches))
        else:
            this_logger.warning("Not found the credible_branches.")

        if 'working_branches' in file_data:
            InputData.working_branches = list_unique(
                file_data["working_branches"])
            this_logger.info("Init working branches num:%d.",
                             len(InputData.working_branches))
        else:
            this_logger.warning("Not found the working_branches.")
    return True


def init():
    """
    初始化
    :return: 无
    """
    # 初始化路径
    init_workspace()
    # 初始化日志
    if this_logger is None:
        print("Init logger failed.")
        return False
    # 获取输入的所有包
    conf_path = get_file_path(CONFIG_PATH, 'input_package_list.txt')
    if not os.path.isfile(conf_path):
        this_logger.error(
            "Not found the inputPackageList.txt file:%s.", conf_path)
        return False
    with open(conf_path, "r") as conf_file:
        for line in conf_file:
            InputData.input_pkg.add(line.strip())
        message = "    ".join(InputData.input_pkg)
        this_logger.debug("Init package list :%s.", message)

    # 初始化搜索分支
    if not init_branches():
        this_logger.error(
            "Init branches failed.")
        return False
    this_logger.info("Init all succeed.")
    return True


def list_unique(input_list):
    """
    list去重
    :param input_list: 待处理list
    :return: 去重之后的list
    """
    ret_list = list(set(input_list))
    ret_list.sort(key=input_list.index)
    return ret_list


def get_pkgship(pkg_name):
    """
    调用pkgship selfbuild 软件包pkgName的所有依赖
    :return: 无，输出在./tmp/pkgshipoutput_package.txt文件中
    """
    if pkg_name == "":
        this_logger.error("pkg_name is null")
        return False
    # 开始计时
    this_logger.info(
        "Get %s pkgship selfbuild start, this may take a few minutes...", pkg_name)
    pkgship_cmd = "pkgship selfbuild -s 1 -t source " + pkg_name
    try:
        output = subprocess.check_output(
            pkgship_cmd, stderr=subprocess.STDOUT, shell=True)
        pkgship_output = output.decode()
        if pkgship_output.find("selfDepend result display") == -1:
            this_logger.error(
                "Get %s pkgship selfbuild failed, output is invalid", pkg_name)
            return False
        output_file_name = "pkgshipoutput_" + pkg_name + ".txt"
        output_file = get_file_path(OUTPUT_PATH, output_file_name)
        with open(output_file, 'w+') as pkgship_output_file:
            pkgship_output_file.write(pkgship_output)
        this_logger.info(
            "Get %s pkgship selfbuild succeed.", pkg_name)
        return True
    except subprocess.TimeoutExpired as time_err:
        this_logger.error(
            "Command:%s is time out.error: %s", pkgship_cmd, time_err)
        return False
    except subprocess.CalledProcessError as call_err:
        this_logger.error(
            "Command:%s is failed.error: %s", pkgship_cmd, call_err.output.decode(encoding="utf-8"))
        return False


def get_package_from_line(line):
    """
    从一行字符串中，分割，获取第一列包名
    例如：
          zziplib              0.13.69             openEuler-20.09
    :param line: 输入行字符串
    :return: 第一列包名
    """
    line_words = line.split()
    if len(line_words) == 0:
        return ""
    return line_words[0]


def pkgship_filter(ret_package_list, package):
    """
    对pkgship查询结果进行过滤，过滤出需要新加的fedora源码包
    :return: 无
    """
    if package == "":
        this_logger.error("pkg_name is null")
        return False
    pkg_file_name = "pkgshipoutput_" + package + ".txt"
    pkg_file_path = get_file_path(OUTPUT_PATH, pkg_file_name)
    if not os.path.isfile(pkg_file_path):
        this_logger.error(
            "Not found the pkgshipoutput file:%s.", pkg_file_name)
        return False
    with open(pkg_file_path, "r") as pkgship_output_file:
        start_filter_flag = FilterStatus.NotStarted
        for line in pkgship_output_file:
            # 源码包的依赖报告起始样式为：
            #    src name                                    version             database
            # 其中如果有package name，则为二进制包的报告，忽略
            if (line.find('src name') != -1) and \
                    (line.find('database') != -1) and \
                    (line.find('package name') == -1):
                # 开始过滤报告内容
                start_filter_flag = FilterStatus.ReadyToStart
                this_logger.info(
                    "Found src package report start flag.")
                continue
            # 只检索带fedora关键字的包，目前sqlite配置为fedora，关键字可以根据实际情况扩展
            if start_filter_flag != FilterStatus.NotStarted and line.find('fedora') != -1:
                start_filter_flag = FilterStatus.AlreadyStarted
                pkg_name = get_package_from_line(line)
                if pkg_name == "":
                    this_logger.warning("Find wrong package name:%s.", pkg_name)
                    continue
                ret_package_list.append(pkg_name)
                # this_logger.debug("Add require package:%s.", pkg_name)
                continue
            # 报告结尾样式为"====="，遇到结尾直接退出循环
            if start_filter_flag == FilterStatus.AlreadyStarted and line.find(
                    '================================================') != -1:
                this_logger.info(
                    "Src package filter end. total num:%d.", len(ret_package_list))
                message = "   ".join(ret_package_list)
                this_logger.debug("Filter result:%s", message)
                return True
    return False


def get_package_exist_branches(pkg_name):
    """
       osc search获取一个软件包已经存在的分支
       :param pkg_name: 待查询包名
       :return: 搜索到的分支列表集合
       """
    if pkg_name == "":
        this_logger.error("pkg_name is null.")
        return False
    search_cmd = "osc search " + pkg_name
    branches = set()
    try:
        output = subprocess.check_output(
            search_cmd, stderr=subprocess.STDOUT, shell=True)
        for line in output.splitlines():
            line_str = line.decode('utf-8')
            if (line_str.find(pkg_name) != -1) and \
                    (line_str.find('No matches found') == -1) and \
                    (line_str.find('matches for') == -1):
                branch_name = str(line_str.strip().split()[0])
                branches.add(branch_name)
                # this_logger.debug("Add branches:%s.", branch_name)

        message = "    ".join(branches)
        this_logger.debug(
            "Get package:%s branches by osc search end. Results:%s.", pkg_name, message)
        this_logger.info(
            "Get package:%s branches by osc search end. Total num:%d.", pkg_name, len(branches))
    except subprocess.TimeoutExpired as time_err:
        this_logger.error(
            "Command:%s is time out.error: %s", search_cmd, time_err)
    except subprocess.CalledProcessError as call_err:
        this_logger.error(
            "Command:%s is failed.error: %s", search_cmd, call_err.output.decode(encoding="utf-8"))

    return branches


def get_pkg_build_status(pkg_name, branch_name):
    """
    判断软件包在指定分支的工程下是否编译成功
    :param pkg_name: 软件包名称
    :param branch_name: 分支名称
    :return: True-构建成功, False-构建失败
    """
    if pkg_name == "" or branch_name == "":
        this_logger.error("pkg_name or branch_name is null.")
        return False
    query_cmd = "osc results " + branch_name + " " + pkg_name
    try:
        output = subprocess.check_output(
            query_cmd, stderr=subprocess.STDOUT, shell=True)
        for line in output.splitlines():
            line_str = line.decode('utf-8')
            if (line_str.find('standard_aarch64') != -1) or \
                    (line_str.find('standard_x86_64') != -1):
                if line_str.find('succeed') != -1:
                    this_logger.debug(
                        "Found exist package: %s in branch:%s.", pkg_name, branch_name)
                    return True
        this_logger.debug(
            "Branch %s build package %s result is not succeed.", branch_name, pkg_name)
        return False
    except subprocess.TimeoutExpired as time_err:
        this_logger.error(
            "Command:%s is time out.error: %s", query_cmd, time_err)
    except subprocess.CalledProcessError as call_err:
        this_logger.error(
            "Command:%s is failed.error: %s", query_cmd, call_err.output.decode(encoding="utf-8"))
    return False


def get_all_pkg_ship():
    """
    获取配置的待查询包列表的所有依赖关系
    :return: 无
    """
    pkgship_output_list = []
    for package in InputData.input_pkg:
        # 1.调用pkgship selfbuild并将结果保存在output文件中
        if not get_pkgship(package):
            this_logger.info(
                "Get pkgship package :%s failed.", package)
            # 如果查询失败，则只把自己加入列表
            pkgship_output_list.append(package)
            continue

        # 2.将结果中的fedora包过滤出来
        if not pkgship_filter(pkgship_output_list, package):
            this_logger.info(
                "Filter pkgship output :%s failed.", package)
            continue

    # 列表去重
    OutputData.all_requires_pkg = list_unique(pkgship_output_list)


def dump_new_pkg():
    """
    将需要新引入的包输出到结果中
    :return: 无
    """
    file_path = get_file_path(OUTPUT_PATH, "new_rpm_list.txt")
    with open(file_path, "w") as new_pkg_file:
        for package in OutputData.new_pkg:
            data = package + "\n"
            new_pkg_file.write(data)
    this_logger.info(
        "Dump new package succeed.sum: %d", len(OutputData.new_pkg))


def dump_found_pkg():
    """
    将已经存在的包输出到结果中
    :return: 无
    """
    file_path = get_file_path(OUTPUT_PATH, "foundPackage.txt")
    with open(file_path, "w") as found_pkg_file:
        found_pkg_file.write(
            "[RESULT] Here are the existing packages in credible branches found:\n")
        for i in OutputData.found_credible_pkg:
            data = i[0] + " " + i[1] + "\n"
            found_pkg_file.write(data)
        found_pkg_file.write(
            "=======================================================\n")
        this_logger.info("Dump existing packages in credible branches succeed. sum:%d.", len(
            OutputData.found_credible_pkg))

        found_pkg_file.write(
            "[RESULT] Here are the existing packages in working branches found:\n")
        for i in OutputData.found_working_pkg:
            data = i[0] + " " + i[1] + "\n"
            found_pkg_file.write(data)
        found_pkg_file.write(
            "=======================================================\n")
        this_logger.info("Dump existing packages in working branches succeed. sum:%d.", len(
            OutputData.found_working_pkg))


def is_factory_to_mainline(package):
    """
    是否需要从Factory分支合入Mainline, 即在Factory中有，在Mainline中没有
    :param package:
    :return: True-需要，False-不需要
    """
    if ([package, 'openEuler:Factory'] in OutputData.found_credible_pkg) and \
            ([package, 'openEuler:Mainline'] not in OutputData.found_credible_pkg):
        return True
    return False


def dump_factory_to_mainline():
    """
    需要从Factory合入Mainline的包输出到文件
    :return: 无
    """
    file_path = get_file_path(OUTPUT_PATH, "foundPackage.txt")
    with open(file_path, "a") as found_pkg_file:
        found_pkg_file.write(
            "[Attention] The following packages may need to be merged "
            "into Mainline from Factory :\n")
        for package in OutputData.factory_to_mainline_pkg:
            data = package + "\n"
            found_pkg_file.write(data)
        found_pkg_file.write(
            "=======================================================\n")
        this_logger.info("Dump Factory branch to Mainline branch succeed. sum:%d.", len(
            OutputData.factory_to_mainline_pkg))


def dump_credible_to_working():
    """
    可以从可信分支拉取到工作分支的包，输出到文件
    :return: 无
    """
    file_path = get_file_path(OUTPUT_PATH, "foundPackage.txt")
    with open(file_path, "a") as found_pkg_file:
        found_pkg_file.write(
            "[Attention] The following packages have been pulled from "
            "credible branch to working branch:\n")
        for i in OutputData.credible_to_working_pkg:
            data = i[0] + " " + i[1] + "\n"
            found_pkg_file.write(data)
        found_pkg_file.write(
            "=======================================================\n")
        this_logger.info("Dump credible branch to working branch succeed. sum:%d.", len(
            OutputData.credible_to_working_pkg))


def identify_valid_branch():
    """
    识别出存在已经引入的包的分支
    :return: 无
    """
    for package in OutputData.all_requires_pkg:
        credible_branch_found_name = ""
        credible_branch_found = False
        working_branch_found = False
        working_branch_existed = False
        # 1.获取这个包存在的分支
        branches = get_package_exist_branches(package)

        for branch in branches:
            if branch in InputData.credible_branches and get_pkg_build_status(package, branch):
                OutputData.found_credible_pkg.append([package, branch])
                credible_branch_found_name = branch \
                    if credible_branch_found_name == "" \
                    else credible_branch_found_name
                credible_branch_found = True

            if branch in InputData.working_branches:
                working_branch_existed = True
                if get_pkg_build_status(package, branch):
                    OutputData.found_working_pkg.append([package, branch])
                    working_branch_found = True

        if not (credible_branch_found or working_branch_found):
            OutputData.new_pkg.append(package)
            this_logger.info(
                "Package %s needs to be introduced.", package)
        elif credible_branch_found:
            if is_factory_to_mainline(package):
                OutputData.factory_to_mainline_pkg.add(package)
                this_logger.info(
                    "Package %s needs to be merge from Factory to Mainline.", package)

            if not working_branch_found:
                branch_exist_package(credible_branch_found_name, package, working_branch_existed)
                this_logger.info(
                    "Package %s can be pull from branch:%s.", package, credible_branch_found_name)

    dump_new_pkg()
    dump_found_pkg()
    dump_factory_to_mainline()
    dump_credible_to_working()


def osc_branch(package, credible_branch, working_branch):
    """
    调用osc branch命令将credible_branch工程下的package包拉到working_branch
    :param package: 软件包名
    :param credible_branch: 已经编好包的分支
    :param working_branch: 想要这个包的分支
    :return: True-成功，False-失败
    """
    if package == "" or credible_branch == "" or working_branch == "":
        this_logger.error("package or credible_branch or working_branch is null.")
        return False
    branch_cmd = "osc branch " + credible_branch + \
                 " " + package + " " + working_branch
    try:
        output = subprocess.check_output(
            branch_cmd, stderr=subprocess.STDOUT, shell=True)
        for line in output.splitlines():
            line_str = line.decode('utf-8')
            if (line_str.find("failed") != -1) or \
                    (line_str.find('error') != -1):
                this_logger.error("osc branch failed. %s", line_str)
                return False
        this_logger.info(
            "Process osc branch :%s succeed.", branch_cmd)
        return True
    except subprocess.TimeoutExpired as time_err:
        this_logger.error(
            "Command:%s is time out.error: %s", branch_cmd, time_err)
    except subprocess.CalledProcessError as call_err:
        this_logger.error(
            "Command:%s is failed.error: %s", branch_cmd, call_err.output.decode(encoding="utf-8"))
    return False


def branch_exist_package(credible_branch, package, working_branch_existed):
    """
    从可信分支拉已经编好的包
    :param credible_branch:
    :param package: 待拉取包名
    :param working_branch_existed: 在分支上已经建仓
    :return: 无
    """
    # 可信分支选择
    for branch in InputData.working_branches:
        print('****This package: {0} can pull form crediable branch: '
              '{1} to working branch:{2}'.format(package, credible_branch, branch))

        if working_branch_existed:
            need_branch = 'no'
            print('****This package: {0} existed in working branch:{1}, '
                  'you should delete it first.'.format(package, branch))
        else:
            need_branch = input("****Do you want me to pull it now? yes/no ")

        if need_branch in ['y', 'Y', 'yes', 'Yes', 'YES']:
            if osc_branch(package, credible_branch, branch):
                status = credible_branch + " -> " + branch + " (done)"
            else:
                status = credible_branch + " -> " + branch + " (todo)"
        else:
            print("****You can pull it later, and find more info in foundPackage.txt")
            status = credible_branch + " -> " + branch + " (todo)"
        # 将这个已存在的包记录下来
        OutputData.credible_to_working_pkg.append([package, status])
        this_logger.debug(
            "Need branch package:%s status:%s.", package, status)


def main():
    """
    main函数
    :return: 无
    """
    # 初始化
    if not init():
        return False
    # 从pkgship查询所有的依赖关系
    get_all_pkg_ship()
    # 识别依赖包是否已经存在，以及所在分支
    identify_valid_branch()
    return True

# if __name__ == '__main__':
#    main()
